package com.googlecode.framework.orm.hibernate.criterion;

import java.util.Collection;

import org.apache.commons.lang.StringUtils;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.PropertyExpression;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.hibernate.type.Type;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

public class SafeRestrictions {

    private SafeRestrictions() {}
    
    /**
     * @see org.hibernate.criterion.Restrictions#idEq(Object value)
     */
    public static Criterion idEqual(Object value) {
        return value == null ? null : Restrictions.idEq(value);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#eq(String propertyName, Object value)
     */
    public static SimpleExpression equal(String propertyName, Object value) {
        return equal(propertyName, value, true);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#eq(String propertyName, Object value)
     */
    public static SimpleExpression equal(String propertyName, Object value, boolean ignoreEmpty) {
        if (ignore(value, ignoreEmpty)) {
            return null;
        }
        return Restrictions.eq(propertyName, value);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#ne(String propertyName, Object value)
     */
    public static SimpleExpression notEqual(String propertyName, Object value) {
        return equal(propertyName, value, true);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#ne(String propertyName, Object value)
     */
    public static SimpleExpression notEqual(String propertyName, Object value, boolean ignoreEmpty) {
        if (ignore(value, ignoreEmpty)) {
            return null;
        }
        return Restrictions.ne(propertyName, value);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#like(String propertyName, String value, MatchMode matchMode)
     */
    public static SimpleExpression likeAnywhere(String propertyName, String value) {
        return like(propertyName, value, MatchMode.ANYWHERE);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#like(String propertyName, String value, MatchMode matchMode)
     */
    public static SimpleExpression likeStart(String propertyName, String value) {
        return like(propertyName, value, MatchMode.START);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#like(String propertyName, String value, MatchMode matchMode)
     */
    public static SimpleExpression likeEnd(String propertyName, String value) {
        return like(propertyName, value, MatchMode.END);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#like(String propertyName, String value, MatchMode matchMode)
     */
    public static SimpleExpression likeExact(String propertyName, String value) {
        return like(propertyName, value, MatchMode.EXACT);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#like(String propertyName, String value, MatchMode matchMode)
     */
    public static SimpleExpression like(String propertyName, String value, MatchMode matchMode) {
        if (ignore(value, true)) {
            return null;
        }
        return Restrictions.like(propertyName, value, matchMode);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#ilike(String propertyName, String value, MatchMode matchMode)
     */
    public static Criterion ilikeAnywhere(String propertyName, String value) {
        return ilike(propertyName, value, MatchMode.ANYWHERE);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#ilike(String propertyName, String value, MatchMode matchMode)
     */
    public static Criterion ilikeStart(String propertyName, String value) {
        return ilike(propertyName, value, MatchMode.START);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#ilike(String propertyName, String value, MatchMode matchMode)
     */
    public static Criterion ilikeEnd(String propertyName, String value) {
        return ilike(propertyName, value, MatchMode.END);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#ilike(String propertyName, String value, MatchMode matchMode)
     */
    public static Criterion ilikeExact(String propertyName, String value) {
        return ilike(propertyName, value, MatchMode.EXACT);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#ilike(String propertyName, String value, MatchMode matchMode)
     */
    public static Criterion ilike(String propertyName, String value, MatchMode matchMode) {
        if (ignore(value, true)) {
            return null;
        }
        return Restrictions.ilike(propertyName, value, matchMode);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#gt(String propertyName, Object value)
     */
    public static SimpleExpression greaterThan(String propertyName, Object value) {
        if (ignore(value, true)) {
            return null;
        }
        return Restrictions.gt(propertyName, value);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#lt(String propertyName, Object value)
     */
    public static SimpleExpression lessThan(String propertyName, Object value) {
        if (ignore(value, true)) {
            return null;
        }
        return Restrictions.lt(propertyName, value);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#le(String propertyName, Object value)
     */
    public static SimpleExpression lessThanEqual(String propertyName, Object value) {
        if (ignore(value, true)) {
            return null;
        }
        return Restrictions.le(propertyName, value);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#ge(String propertyName, Object value)
     */
    public static SimpleExpression greateThanEqual(String propertyName, Object value) {
        if (ignore(value, true)) {
            return null;
        }
        return Restrictions.ge(propertyName, value);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#between(String propertyName, Object lo, Object hi)
     */
    public static Criterion between(String propertyName, Object lo, Object hi) {
        if (ignore(hi, true) && ignore(lo, true)) {
            return null;
        }
        return Restrictions.between(propertyName, lo, hi);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#in(String propertyName, Object[] values)
     */
    public static Criterion in(String propertyName, Object[] values) {
        if (ObjectUtils.isEmpty(values)) {
            return null;
        }
        return Restrictions.in(propertyName, values);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#in(String propertyName, Collection values)
     */
    public static Criterion in(String propertyName, Collection<?> values) {
        if (CollectionUtils.isEmpty(values)) {
            return null;
        }
        return Restrictions.in(propertyName, values);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#isNull(String propertyName)
     */
    public static Criterion isNull(String propertyName) {
        return Restrictions.isNull(propertyName);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#eqProperty(String propertyName, String otherPropertyName)
     */
    public static PropertyExpression equalProperty(String propertyName, String otherPropertyName) {
        return Restrictions.eqProperty(propertyName, otherPropertyName);
    }
    /**
     * @see org.hibernate.criterion.Restrictions#neProperty(String propertyName, String otherPropertyName)
     */
    public static PropertyExpression notEqualProperty(String propertyName, String otherPropertyName) {
        return Restrictions.neProperty(propertyName, otherPropertyName);
    }
    /**
     * @see org.hibernate.criterion.Restrictions#ltProperty(String propertyName, String otherPropertyName)
     */
    public static PropertyExpression lessThanProperty(String propertyName, String otherPropertyName) {
        return Restrictions.ltProperty(propertyName, otherPropertyName);
    }
    /**
     * @see org.hibernate.criterion.Restrictions#leProperty(String propertyName, String otherPropertyName)
     */
    public static PropertyExpression lessThanEqualProperty(String propertyName, String otherPropertyName) {
        return Restrictions.leProperty(propertyName, otherPropertyName);
    }
    /**
     * @see org.hibernate.criterion.Restrictions#gtProperty(String propertyName, String otherPropertyName)
     */
    public static PropertyExpression greaterThanProperty(String propertyName, String otherPropertyName) {
        return Restrictions.gtProperty(propertyName, otherPropertyName);
    }
    /**
     * @see org.hibernate.criterion.Restrictions#geProperty(String propertyName, String otherPropertyName)
     */
    public static PropertyExpression greaterThanEqualProperty(String propertyName, String otherPropertyName) {
        return Restrictions.geProperty(propertyName, otherPropertyName);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#isNotNull(String propertyName)
     */
    public static Criterion isNotNull(String propertyName) {
        return Restrictions.isNotNull(propertyName);
    }
    
    /**
     * Return the conjuction
     * 
     * @param criterions
     * @return
     */
    public static Criterion and(Criterion... criterions) {
        if (ObjectUtils.isEmpty(criterions)) {
            return null;
        }
        
        Conjunction cj = Restrictions.conjunction();
        for (Criterion criterion : criterions) {
            if (criterion != null) {
                cj.add(criterion);
            }
        }
        return cj;
    }
    
    /**
     * Return the disjunction
     * 
     * @param criterions
     * @return
     */
    public static Criterion or(Criterion... criterions) {
        if (ObjectUtils.isEmpty(criterions)) {
            return null;
        }
        
        Disjunction dj = Restrictions.disjunction();
        for (Criterion criterion : criterions) {
            if (criterion != null) {
                dj.add(criterion);                
            }
        }
        return dj;
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#not(Criterion criterion)
     */
    public static Criterion not(Criterion criterion) {
        if (criterion == null) {
            return null;
        }
        return Restrictions.not(criterion);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#sqlRestriction(String sql, Object[] values, Type[] types)
     */
    public static Criterion sqlRestriction(String sql, Object[] values, Type[] types) {
        return Restrictions.sqlRestriction(sql, values, types);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#sqlRestriction(String sql, Object value, Type type)
     */
    public static Criterion sqlRestriction(String sql, Object value, Type type) {
        return Restrictions.sqlRestriction(sql, value, type);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#sqlRestriction(String sql)
     */
    public static Criterion sqlRestriction(String sql) {
        return Restrictions.sqlRestriction(sql);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#isEmpty(String propertyName)
     */
    public static Criterion isEmpty(String propertyName) {
        return Restrictions.isEmpty(propertyName);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#isNotEmpty(String propertyName)
     */
    public static Criterion isNotEmpty(String propertyName) {
        return Restrictions.isNotEmpty(propertyName);
    }
    
    /**
     * @see org.hibernate.criterion.Restrictions#sizeEq(String propertyName, int size)
     */
    public static Criterion sizeEqual(String propertyName, int size) {
        return Restrictions.sizeEq(propertyName, size);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#sizeNe(String propertyName, int size)
     */
    public static Criterion sizeNotEqual(String propertyName, int size) {
        return Restrictions.sizeNe(propertyName, size);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#sizeGt(String propertyName, int size)
     */
    public static Criterion sizeGreaterThan(String propertyName, int size) {
        return Restrictions.sizeGt(propertyName, size);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#sizeLt(String propertyName, int size)
     */
    public static Criterion sizeLessThan(String propertyName, int size) {
        return Restrictions.sizeLt(propertyName, size);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#sizeGe(String propertyName, int size)
     */
    public static Criterion sizeGreaterThanEqual(String propertyName, int size) {
        return Restrictions.sizeGe(propertyName, size);
    }

    /**
     * @see org.hibernate.criterion.Restrictions#sizeLe(String propertyName, int size)
     */
    public static Criterion sizeLessThanEuqal(String propertyName, int size) {
        return Restrictions.sizeLe(propertyName, size);
    }
    
    // --- Subqueries 
    
    protected static boolean ignore(Object value, boolean ignoreEmpty) {
        if (value == null) {
            return true;
        }
        if (value instanceof String) {
            String str = (String) value;
            return ignoreEmpty && StringUtils.isEmpty(str);
        } else {
            return false;
        }
    }
}
